/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package uk.co.drpj;

import com.sun.media.sound.AudioSynthesizer;
import java.io.IOException;
import java.util.List;
import java.util.Observable;
import java.util.Observer;
import java.util.Vector;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.sound.midi.InvalidMidiDataException;
import javax.sound.midi.MidiDevice;
import javax.sound.midi.MidiDevice.Info;
import javax.sound.midi.MidiSystem;
import javax.sound.midi.MidiUnavailableException;
import javax.sound.midi.Receiver;
import javax.sound.midi.Soundbank;
import javax.sound.midi.Synthesizer;
import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.JOptionPane;

import uk.co.drpj.audio.TootSynthAdapter;
import uk.org.toot.audio.core.AudioBuffer;
import uk.org.toot.audio.core.AudioProcess;
import uk.org.toot.audio.server.AudioServerConfiguration;
import uk.org.toot.audio.server.AudioServerServices;
import uk.org.toot.audio.server.IOAudioProcess;
import uk.org.toot.audio.server.MultiIOJavaSoundAudioServer;
import uk.org.toot.swingui.audioui.serverui.AudioServerUIServices;

/**
 *
 *  This class setups up the audio and midisystem
 *  
 *  I particular it makes sure the the Gervill soft synth is using the Toot audio
 *  so we get low latency.
 *  
 *  
 *
 *
 * @author pjl
 */
public class AudioAndMidiSystem {

    private static AudioAndMidiSystem instance;
    public Receiver recv;
    AudioSynthesizer audioSynth;
    public MidiDevice odev = null;
    private boolean adminMode = true;
    private MultiIOJavaSoundAudioServer server;
    private AudioServerConfiguration serverConfig;
    private Vector<AudioProcess> clients = new Vector<AudioProcess>();
    MidiDevice idev[] = {null};
    JFrame configureFrame;

    public static AudioAndMidiSystem instance() {
        if (instance == null) {
            try {
                instance = new AudioAndMidiSystem();
            } catch (Exception ex) {
                Logger.getLogger(AudioAndMidiSystem.class.getName()).log(Level.SEVERE, null, ex);
            }
        }
        return instance;

    }

    public AudioAndMidiSystem() throws Exception {
        String admStr = Config.getProperty("adminMode");

        if (admStr == null) {
            adminMode = true;
        } else {
            adminMode = Boolean.parseBoolean(admStr);
        }

        // open the audio server.
        server = new MultiIOJavaSoundAudioServer();

        List<String> listA = server.getAvailableOutputNames();
        Object a[] = new Object[listA.size()];
        a = listA.toArray(a);

        String audioOut = Config.getProperty("audioOutDev");

//        if (audioOut == null || (!listA.contains(audioOut))) {
//            adminMode = true;
//        }

        Object selectedValue = audioOut;
        if (adminMode) {
            selectedValue = JOptionPane.showInputDialog(null,
                    "Select audio output", "Select audio output device", JOptionPane.INFORMATION_MESSAGE,
                    null, a, audioOut);

            if (selectedValue == null) {
                System.exit(0);
            }

            Config.setProperty("audioOutDev", selectedValue.toString());
        }

        final IOAudioProcess out = server.openAudioOutput((String) selectedValue,
                "output");

        Info[] infos = MidiSystem.getMidiDeviceInfo();


        int cnt = 0;

        Vector<MidiDevice.Info> list = new Vector<MidiDevice.Info>();
        for (Info info : infos) {
            MidiDevice dev;
            try {
                dev = MidiSystem.getMidiDevice(info);
                if (dev.getMaxTransmitters() != 0) {
                    list.add(info);

                }
                if (dev.getMaxReceivers() != 0) {

                    if (dev.getDeviceInfo().toString().startsWith("Gervill")) {
                        odev = dev;
                    }

                }
            } catch (MidiUnavailableException e1) {
                // TODO Auto-generated catch block
                e1.printStackTrace();
            }
            cnt++;
        }

        a = new Object[list.size() + 1];
        a = list.toArray(a);
        a[list.size()] = "None";


        for (int i = 0; i < idev.length; i++) {
            String midiIn = Config.getProperty("midiInDev_" + i);

            for (Object inf : a) {
                if (inf.toString().equals(midiIn)) {
                    selectedValue = inf;
                    break;
                }
            }


            if (adminMode) {
                selectedValue = JOptionPane.showInputDialog(null,
                        "MIDI IN device", "Select MIDI IN device(" + i +
                        " of " + idev.length, JOptionPane.INFORMATION_MESSAGE,
                        null, a, selectedValue);

                if (selectedValue == null) {
                    System.exit(0);
                }

                Config.setProperty("midiInDev_" + i, selectedValue.toString());

            }
            if (selectedValue instanceof Info) {
                idev[i] = MidiSystem.getMidiDevice((Info) selectedValue);
            } else {
                idev[i] = null;
            }
        }

        audioSynth = (AudioSynthesizer) odev;

        AudioProcess fork = new AudioProcess() {

            public void open() throws Exception {
            }

            public int processAudio(AudioBuffer arg0) {
                out.processAudio(arg0);
                //  buffer.in.processAudio(arg0);
                for (AudioProcess client : clients) {
                    client.processAudio(arg0);
                }
                return AUDIO_OK;
            }

            public void close() throws Exception {
            }
        };

        TootSynthAdapter.connect(audioSynth, server, fork);


        recv = odev.getReceiver();

        if (Config.DEFAULT_SOUND_BANK.exists()) {

            if (Config.DEFAULT_SOUND_BANK != null) {
                Soundbank bank;
                try {
                    bank = MidiSystem.getSoundbank(Config.DEFAULT_SOUND_BANK);


                    if (bank != null) {
                        audioSynth.loadAllInstruments(bank);
                        System.out.println(" Loaded default.sf2");
                    }
                } catch (InvalidMidiDataException ex) {
                    Logger.getLogger(DemoMain.class.getName()).log(Level.SEVERE, null, ex);
                } catch (IOException ex) {
                    Logger.getLogger(DemoMain.class.getName()).log(Level.SEVERE, null, ex);
                }

            }

        } else {
            JOptionPane.showMessageDialog(null, " Could not find a sound font \n No big deal I'll use the emergency sound font." +
                    "\n If you want to put a use a nice sound font put it \n" + Config.DEFAULT_SOUND_BANK);
        }
        serverConfig = AudioServerServices.createServerConfiguration(server);
        serverConfig.applyProperties(Config.getProperties());
     // shutdown hook to close the audio devices.
		Runtime.getRuntime().addShutdownHook(new Thread() {

			@Override
			public void run() {

				System.out.println("Stop...");
				server.stop();

				try {
				//	server.closeAudioInput(in);
					server.closeAudioOutput(out);
				} catch (Exception e) {
					e.printStackTrace();
				}

			}
		});

    }

    /**
     * Start audio server 
     */
    public void start() {
        server.start();
    }

    /**
     * Add a client to the audio system
     * @param proc
     */
    void addClient(AudioProcess proc) {
        clients.add(proc);
    }

    public Synthesizer getSynth() {
        return audioSynth;
    }

    public Receiver getReceiver() {
        return recv;
    }

    public MidiDevice[] getMidiInputs() {
        return idev;
    }

    /**
     *
     * Allow user to play with server parameters.
     *
     */
    public void configure() {



        if (configureFrame != null) {
            configureFrame.setVisible(true);
            return;
        }

        final JComponent ui = AudioServerUIServices.createServerUI(server,
                serverConfig);

        serverConfig.addObserver(new Observer() {

            public void update(Observable arg0, Object arg1) {
                serverConfig.mergeInto(Config.getProperties());
                Config.store();
            }
        });

        if (ui == null) {
            return; // no server ui
        }
        configureFrame = new JFrame("Audio Settings");
        // configureFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        configureFrame.setAlwaysOnTop(true);
        configureFrame.setContentPane(ui);
        configureFrame.pack();
        configureFrame.setVisible(true);
    }

 
}
